﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the Microsoft Public License (Ms-PL).
// See accompanying file License.txt or copy at http://mews.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Mews.Mime
{
    using System;

    /// <summary>Provides visitation of message elements up to the maximum recursion depth specified during
    /// construction.</summary>
    /// <threadsafety static="true" instance="false"/>
    public abstract class MessageVisitor : IMessageVisitor
    {
        private readonly int maxRecursionDepth;
        private int recursionDepth;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Throws <see cref="NotImplementedException"/>.</summary>
        /// <exception cref="NotImplementedException">Thrown with each call.</exception>
        public void OnVisit(Body visitable)
        {
            throw GetNotImplementedException(visitable);
        }

        /// <summary>Visits an <see cref="ApplicationBody"/>.</summary>
        public abstract void OnVisit(ApplicationBody visitable);

        /// <summary>Visits an <see cref="ApplicationBody"/>.</summary>
        public abstract void OnVisit(ImageBody visitable);

        /// <summary>Visits a <see cref="TextBody"/>.</summary>
        public abstract void OnVisit(TextBody visitable);

        /// <summary>Visits <paramref name="visitable"/>.<see cref="MessageBody.Content"/>.</summary>
        /// <remarks>If the subclass needs to do further processing then this method should be overridden and called
        /// before and/or after doing so.</remarks>
        public virtual void OnVisit(MessageBody visitable)
        {
            this.Recurse(() => visitable.Content.Accept(this));
        }

        /// <summary>Visits all elements in <paramref name="visitable"/>.<see cref="MultipartBody.Parts"/>.
        /// </summary>
        /// <remarks>If the subclass needs to do further processing then this method should be overridden and called
        /// before and/or after doing so.</remarks>
        public virtual void OnVisit(MultipartBody visitable)
        {
            this.Recurse(
                () =>
                {
                    foreach (BodyPart part in visitable.Parts)
                    {
                        part.Accept(this);
                    }
                });
        }

        /// <summary>Throws <see cref="NotImplementedException"/>.</summary>
        /// <exception cref="NotImplementedException">Thrown with each call.</exception>
        public void OnVisit(Entity visitable)
        {
            throw GetNotImplementedException(visitable);
        }

        /// <summary>Visits <paramref name="visitable"/>.<see cref="Entity.Body"/>.</summary>
        /// <remarks>If the subclass needs to do further processing then this method should be overridden and called
        /// before and/or after doing so.</remarks>
        public virtual void OnVisit(BodyPart visitable)
        {
            this.RecurseEntity(visitable);
        }

        /// <summary>Visits <paramref name="visitable"/>.<see cref="Entity.Body"/>.</summary>
        /// <remarks>If the subclass needs to do further processing then this method should be overridden and called
        /// before and/or after doing so.</remarks>
        public virtual void OnVisit(Message visitable)
        {
            this.RecurseEntity(visitable);
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Initializes a new instance of the <see cref="MessageVisitor"/> class by calling
        /// <see cref="M:MessageVisitor(int)"/>(<see cref="int"/>.<see cref="int.MaxValue"/>).</summary>
        protected MessageVisitor() : this(int.MaxValue)
        {
        }

        /// <summary>Initializes a new instance of the <see cref="MessageVisitor"/> class.</summary>
        /// <param name="maxRecursionDepth">The maximum recursion depth the visitor will advance to, see
        /// <see cref="RecursionDepth"/> for more information.</param>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="maxRecursionDepth"/> is negative.</exception>
        protected MessageVisitor(int maxRecursionDepth)
        {
            if (maxRecursionDepth < 0)
            {
                throw new ArgumentOutOfRangeException("maxRecursionDepth", ExceptionMessages.NonnegativeNumberRequired);
            }

            this.maxRecursionDepth = maxRecursionDepth;
        }

        /// <summary>Gets the zero-based recursion depth.</summary>
        /// <remarks>
        /// <para>Is zero while visiting the root element (the one which the visitor was originally passed to). Is
        /// incremented before visiting possibly nested elements and decremented right after doing so.</para>
        /// <para>The following types have nested elements: <see cref="Message"/>, <see cref="BodyPart"/>,
        /// <see cref="MessageBody"/>, <see cref="MultipartBody"/>.</para>
        /// </remarks>
        protected int RecursionDepth
        {
            get { return this.recursionDepth; }
        }

        /// <summary>Gets a <see cref="NotImplementedException"/> with the message that no implementation was found for
        /// the type of <paramref name="visitable"/>.</summary>
        protected static Exception GetNotImplementedException(object visitable)
        {
            return new NotImplementedException("No implementation found for " + visitable.GetType() + ".");
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private void RecurseEntity(Entity visitable)
        {
            if (visitable.Body != null)
            {
                this.Recurse(() => visitable.Body.Accept(this));
            }
        }

        private void Recurse(Action action)
        {
            if (this.recursionDepth < this.maxRecursionDepth)
            {
                ++this.recursionDepth;

                try
                {
                    action();
                }
                finally
                {
                    --this.recursionDepth;
                }
            }
        }
    }
}
